package org.koyad.portal.context.listeners;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;

import javassist.ClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.xml.namespace.QName;

import org.koyad.portal.annotations.Action;
import org.koyad.portal.annotations.TagCordinates;
import org.koyad.portal.constants.FrameworkConstants;
import org.koyad.portal.exceptions.ErrorCodes;
import org.koyad.portal.exceptions.FrameworkException;
import org.koyad.portal.utils.LogUtils;

/**
 * This will do following things -
 * <ul>
 * <li>Scans web app classpath and stores actions mappings/action forwards in
 * servlet context.
 * <li>Loads localization files
 * </ul>
 * 
 * @author koyad
 */

@WebListener
public class PistonContextListener implements ServletContextListener {

	private static final LogUtils LOGGER = LogUtils
			.getLogger(PistonContextListener.class);

	private static final String PATH_TO_CLASSES = "/WEB-INF/classes";

	private static final String PATH_TO_LIB = "/WEB-INF/lib";

	@Override
	public void contextInitialized(ServletContextEvent servletContextEvent) {

		LOGGER.startMethod("contextInitialized");

		ClassPool classPool = ClassPool.getDefault();

		ClassPath classPath = new LoaderClassPath(servletContextEvent
				.getServletContext().getClassLoader());
		classPool.insertClassPath(classPath);
		
		try {
		getActionClassFromPath(PATH_TO_CLASSES, servletContextEvent.getServletContext(),
				classPool);

		getActionClassFromLibPath(PATH_TO_LIB, servletContextEvent.getServletContext(),
				classPool); 
		} catch (Exception ex) {
			LOGGER.logException(ex);
		}
		
		LOGGER.endMethod("contextInitialized");

	}
	
	@SuppressWarnings("unchecked")
	private void addActionClass(String fullyQfdName, ClassPool classPool, ServletContext servletContext) throws FrameworkException {
		CtClass ctClass;
		try {
			ctClass = classPool.get(fullyQfdName);
			if (!ctClass.isAnnotation()) {
				AnnotationsAttribute visible = (AnnotationsAttribute) ctClass
						.getClassFile().getAttribute(
								AnnotationsAttribute.visibleTag);
				if (null != visible){
					//ACTIONS
					if(null != visible.getAnnotation(Action.class.getName())) {
						Annotation anno = visible.getAnnotation(Action.class
								.getName());
						
						String name = anno.getMemberValue("name").toString();
						name = name.substring(1, name.length()-1);
						
						Map<String, String> actionMappings = (Map<String, String>)servletContext.getAttribute(FrameworkConstants.PISTON_ACTIONS);
						if(null == actionMappings){
							actionMappings = new HashMap<String, String>();
							servletContext.setAttribute(FrameworkConstants.PISTON_ACTIONS, actionMappings);
						} else if(null != actionMappings.get(name)){
							FrameworkException frameworkExcpetion = new FrameworkException(ErrorCodes.FWKCL001);
							LOGGER.logException(frameworkExcpetion);
							throw frameworkExcpetion;
						}
						actionMappings.put(name, fullyQfdName);
					//TAGS
					}else if(null != visible.getAnnotation(TagCordinates.class.getName())){
						Annotation anno = visible.getAnnotation(TagCordinates.class
								.getName());
						
						String uri = anno.getMemberValue("uri").toString();
						uri = uri.substring(1, uri.length()-1); 
						
						String localName = anno.getMemberValue("localName").toString();
						localName = localName.substring(1, localName.length()-1);
						
						QName qname = new QName(uri, localName);
						
//						String key = anno.getMemberValue("uri").toString() + "-" + anno.getMemberValue("localName").toString();
//						LOGGER.info("key : " + key);
						
						Map<QName, String> tagMappings = (Map<QName, String>)servletContext.getAttribute(FrameworkConstants.PISTON_TAGS);
						if(null == tagMappings){
							tagMappings = new HashMap<QName, String>();
							servletContext.setAttribute(FrameworkConstants.PISTON_TAGS, tagMappings);
						} else if(null != tagMappings.get(qname)) {
							FrameworkException frameworkExcpetion = new FrameworkException(ErrorCodes.FWKCL001);
							LOGGER.logException(frameworkExcpetion);
							throw frameworkExcpetion;
						}
						tagMappings.put(qname, fullyQfdName);
					}
				}
			}
		} catch (NotFoundException ex) {
			LOGGER.logException(ex);
		}
	}
	
	private void getActionClassFromPath(String path, ServletContext servletContext,
			ClassPool classPool) throws FrameworkException {
		Set<String> classes = servletContext.getResourcePaths(path);
		for (String cls : classes) {
			if (cls.indexOf('.') == -1) {
				String newPath = cls.substring(0, cls.length() - 1);
				getActionClassFromPath(newPath, servletContext, classPool);
			} else if (cls.endsWith("class")) {
				String fullyQfdName = cls.substring(
						PATH_TO_CLASSES.length() + 1, cls.lastIndexOf("."))
						.replace("/", ".");
				addActionClass(fullyQfdName, classPool, servletContext);
			}
		}
	}

	private void getActionClassFromLibPath(String pathToLib, ServletContext servletContext,
			ClassPool classPool) throws FrameworkException {
		Set<String> jars = servletContext.getResourcePaths(pathToLib);
		for (String jar : jars) {
			if (jar.indexOf("piston") > -1) {
				LOGGER.info("Jar : " + jar);
				JarInputStream jis = null;
				try {
					jis = new JarInputStream(
							servletContext.getResourceAsStream(jar));
					if (null != jis) {
						while (jis.available() == 1) {
							ZipEntry zipEntry = jis.getNextEntry();
							if (null != zipEntry) {
								String path = zipEntry.getName();
								if (path.endsWith(".class")) {
									System.out.println("Class : " + path);
									String fullyQfdName = path.substring(0,
											path.lastIndexOf(".")).replace("/",
											".");
									addActionClass(fullyQfdName, classPool, servletContext);
								}
							}
						}
					}
				} catch (IOException ex) {
					LOGGER.logException(ex);
				} finally {
					if (null != jis) {
						try {
							jis.close();
						} catch (IOException ex) {
							LOGGER.logException(ex);
						}
					}
				}
			}
		}

	}

	@Override
	public void contextDestroyed(ServletContextEvent servletContextEvent) {
		LOGGER.startMethod("contextDestroyed");
		
		LOGGER.endMethod("contextDestroyed");
	}

	

	
}
